package task

import (
	"cm2/exception"
	"cm2/exception/taskexception"
	"cm2/service/task/jobs"
	"cm2/service/task/tasks"
	"cm2/service/task/tcp"
	"encoding/json"
	"fmt"
	"net"
	"time"

	"github.com/kataras/golog"
)

// Server 任务TCP服务端
type Server struct {
	SId                     string
	SName                   string
	SType                   string
	Server                  *tcp.Server
	Address                 string
	clients                 map[string]*Client
	createJobLogFn          func(serial string, sid string, code string, name string, tasks []*tasks.Task, state int)
	updateJobTaskLogStateFn func(mode string, serial string, state int, message string, updatedOn time.Time)
}

// NewServer create a new Task TCP Server
func NewServer(sid string, sname string, stype string, addr string) *Server {
	tts := &Server{
		SId:     sid,
		SName:   sname,
		SType:   stype,
		Address: addr,
		Server:  tcp.NewServer(sname, addr),
	}

	tts.Server.SetOnDisconnectFn(tts.onClientDisconnected)

	return tts
}

// SetLogFn 设置 Log 调用方法
func (tts *Server) SetLogFn(
	createJobLogFn func(serial string, sid string, code string, name string, tasks []*tasks.Task, state int),
	updateJobTaskLogStateFn func(mode string, serial string, state int, message string, updatedOn time.Time),
) {
	tts.createJobLogFn = createJobLogFn
	tts.updateJobTaskLogStateFn = updateJobTaskLogStateFn
}

// Clients 获取已链接的客户端
func (tts *Server) Clients() map[string]*Client {
	return tts.clients
}

// SetClients 设置配置的客户端id集合
func (tts *Server) SetClients(ids []interface{}) {
	tts.clients = map[string]*Client{}

	if ids == nil || len(ids) <= 0 {
		return
	}

	for _, id := range ids {
		tts.clients[id.(string)] = nil
		tts.Logger().Infof("task service register client %s", id.(string))
	}
}

// SetClient 设置客户端映射 sid => *Client
func (tts *Server) SetClient(id string, clnt *Client) {
	if tts.clients == nil {
		tts.clients = map[string]*Client{}
	}

	_, ok := tts.clients[id]
	if !ok {
		tts.Server.DeleteConn(clnt.Address)
	} else {
		tts.clients[id] = clnt
		tts.Logger().Infof("link worker %s to %v", id, clnt)
	}
}

// SetLogger 设置日志
func (tts *Server) SetLogger(logger *golog.Logger) {
	tts.Server.SetLogger(logger)
}

// Logger 返回日志记录器
func (tts *Server) Logger() *golog.Logger {
	return tts.Server.Logger()
}

// SetReadRetryParams 设置连接读取的重试参数
func (tts *Server) SetReadRetryParams(times, duration int) {
	tts.Server.SetReadRetryParams(times, duration)
}

// SetOnConnectedFn 设置当有新连接接入时的自定义处理 TODO: 没用就去掉
func (tts *Server) SetOnConnectedFn(fn func(*net.TCPConn, *tcp.Server, *golog.Logger)) {
	tts.Server.SetOnConnectedFn(fn)
}

// Start 启动服务
func (tts *Server) Start() error {
	return tts.Server.Start()
}

// Shutdown 关闭服务
func (tts *Server) Shutdown() {
	tts.Server.Shutdown()
}

// SetOnReportReceived 设置当服务端接收到回报时进行自定义任务处理
func (tts *Server) SetOnReportReceived(jpFn func(*Report, error)) {
	pkgFn := func(pkg *tcp.Package, conn *net.TCPConn, svr *tcp.Server, logger *golog.Logger) {
		// 从pkg中拿出 job
		var rpt *Report
		if err := json.Unmarshal(pkg.Data, &rpt); err != nil {
			logger.Errorf("decoding report data failed, %v", err)
			jpFn(nil, err)
		} else {
			logger.Infof("new report received. mode: %s", rpt.Mode)
			// 检查是否为身份报告
			if rpt.Mode == IdentityReport {
				id := (rpt.Data.(map[string]interface{}))
				addr := conn.RemoteAddr().String()
				clnt := &Client{
					SId:     id["SId"].(string),
					SName:   id["SName"].(string),
					SType:   id["SType"].(string),
					Address: addr,
				}
				tts.SetClient(clnt.SId, clnt)
			} else {
				if jpFn == nil {
					logger.Warn("report handler is not specified")
				} else {
					jpFn(rpt, nil)
				}
			}
		}
	}
	tts.Server.SetOnPkgFn(pkgFn)
}

// BeginSendJob 向指定Worker发送任务
func (tts *Server) BeginSendJob(sid string, job *jobs.Job) *exception.Exception {
	// 保存job到数据库
	tks := []*tasks.Task{}
	for _, ts := range job.Tasks {
		tks = append(tks, ts.InnerTask())
	}
	tts.createJobLogFn(
		job.Serial,
		job.SId,
		job.Code,
		job.Name,
		tks,
		JobSending,
	)
	// 从clients中取
	clnt, ok := tts.clients[sid]
	now := time.Now()
	if !ok {
		ex := exception.NewExceptionWithDefine(taskexception.WorkerNotRegister, sid)
		tts.updateJobTaskLogStateFn(JobExecuteReport, job.Serial, JobError, fmt.Sprintf("%v", ex), now)
		return ex
	}
	if clnt == nil {
		ex := exception.NewExceptionWithDefine(taskexception.ConnectionNotLinked, sid)
		tts.updateJobTaskLogStateFn(JobExecuteReport, job.Serial, JobError, fmt.Sprintf("%v", ex), now)
		return ex
	}

	return tts.BeginSendJobToAddr(clnt.Address, job)
}

// BeginSendJobToAddr 向指定地址发送任务
func (tts *Server) BeginSendJobToAddr(addr string, job *jobs.Job) *exception.Exception {
	// 根据地址获取 con
	conn := tts.Server.Conn(addr)
	now := time.Now()
	if conn == nil {
		ex := exception.NewExceptionWithDefine(taskexception.ConnectionNotFound, addr)
		tts.updateJobTaskLogStateFn(JobExecuteReport, job.Serial, JobError, fmt.Sprintf("%v", ex), now)
		return ex
	}

	go func() {
		// 通过连接发送
		err := tcp.SendPackage(
			job.Serial,
			conn,
			job,
			tts.handleSendingJobError,
			tts.handleSendingJobOk,
			tts.Logger(),
		)
		now = time.Now()
		if err != nil {
			ex := exception.NewExceptionWithDefine(taskexception.SendingPackageFailed, err)
			tts.updateJobTaskLogStateFn(JobExecuteReport, job.Serial, JobError, fmt.Sprintf("%v", ex), now)
		}
	}()

	return nil
}

func (tts *Server) handleSendingJobError(data interface{}, err error) {
	job := data.(*jobs.Job)

	ex := exception.NewExceptionWithDefine(taskexception.SendingPackageFailed, err)
	tts.updateJobTaskLogStateFn(JobExecuteReport, job.Serial, JobError, fmt.Sprintf("%v", ex), time.Now())
}

func (tts *Server) handleSendingJobOk(data interface{}) {
	job := data.(*jobs.Job)

	tts.updateJobTaskLogStateFn(JobExecuteReport, job.Serial, JobSended, "", time.Now())
}

func (tts *Server) onClientDisconnected(addr string, force bool, server *tcp.Server, logger *golog.Logger) {
	if tts.clients == nil || len(tts.clients) <= 0 {
		return
	}

	foundKey := ""
	for key, clnt := range tts.clients {
		if addr == clnt.Address {
			foundKey = key
			break
		}
	}

	if foundKey != "" {
		tts.clients[foundKey] = nil
	}
}
